import ContextPadProvider from "bpmn-js/lib/features/context-pad/ContextPadProvider";
import Canvas from "diagram-js/lib/core/Canvas";
import { isAny } from "bpmn-js/lib/features/modeling/util/ModelingUtil";
import { isEventSubProcess, isExpanded } from "bpmn-js/lib/util/DiUtil";
import { getChildLanes } from "bpmn-js/lib/features/modeling/util/LaneUtil";
import { is } from "bpmn-js/lib/util/ModelUtil";
import { assign, forEach, isArray } from 'min-dash';
import AutoPlace from "bpmn-js/lib/features/auto-place/BpmnAutoPlace";

export default class CustomContextPadProvider extends ContextPadProvider {
  static $inject = [
    'config.contextPad',
    'injector',
    'eventBus',
    'contextPad',
    'modeling',
    'elementFactory',
    'connect',
    'create',
    'popupMenu',
    'canvas',
    'rules',
    'translate'
  ]

  constructor(config, injector, eventBus, contextPad, modeling, elementFactory, connect, create, popupMenu, canvas, rules, translate) {
    super(config, injector, eventBus, contextPad, modeling, elementFactory, connect, create, popupMenu, canvas, rules, translate)
    this._contextPad = contextPad
    this._modeling = modeling
    this._elementFactory = elementFactory
    this._connect = connect
    this._create = create
    this._popupMenu = popupMenu
    this._canvas = canvas
    this._rules = rules
    this._translate = translate
    if (config?.autoPlace !== false) {
      this._autoPlace = injector.get('autoPlace', false);
    }
  }

  getContextPadEntries(element) {
    const contextPad = this._contextPad
    const modeling = this._modeling
    const elementFactory = this._elementFactory
    const connect = this._connect
    const create = this._create
    const popupMenu = this._popupMenu
    const canvas = this._canvas
    const rules = this._rules
    const translate = this._translate
    const autoPlace = this._autoPlace

    const actions = {};

    if (element.type === 'label') {
      return actions;
    }

    const businessObject = element.businessObject;

    function startConnect(event, start) {
      connect.start(event, element);
    }

    function removeElement(e) {
      modeling.removeElements([ element ]);
    }

    function getReplaceMenuPosition(element) {
      const Y_OFFSET = 5;
      const diagramContainer = canvas.getContainer()
      const pad = contextPad.getPad(element).html;

      const diagramRect = diagramContainer.getBoundingClientRect()
      const padRect = pad.getBoundingClientRect();

      const top = padRect.top - diagramRect.top;
      const left = padRect.left - diagramRect.left;

      const pos = {
        x: left,
        y: top + padRect.height + Y_OFFSET
      };

      return pos;
    }

    function appendAction(type, className, title, options) {
      if (typeof title !== 'string') {
        options = title;
        title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
      }

      function appendStart(event, element) {
        const shape = elementFactory.createShape(assign({ type: type }, options));
        create.start(event, shape, {
          source: element
        });
      }

      const append = autoPlace ? function(event, element) {
        const shape = elementFactory.createShape(assign({ type: type }, options));
        autoPlace.append(element, shape);
      } : appendStart;

      return {
        group: 'model',
        className: className,
        title: title,
        action: {
          dragstart: appendStart,
          click: append
        }
      };
    }

    function appendGroupAction(group, type, className, title, options) {
      if (typeof title !== 'string') {
        options = title;
        title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
      }

      function appendStart(event, element) {
        const shape = elementFactory.createShape(assign({ type: type }, options));
        create.start(event, shape, {
          source: element
        });
      }

      const append = autoPlace ? function(event, element) {
        const shape = elementFactory.createShape(assign({ type: type }, options));
        autoPlace.append(element, shape);
      } : appendStart;

      return {
        group,
        className: className,
        title: title,
        action: {
          dragstart: appendStart,
          click: append
        }
      };
    }

    function splitLaneHandler(count) {
      return function(event, element) {
        modeling.splitLane(element, count);
        contextPad.open(element, true);
      };
    }

    if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(element)) {
      const childLanes = getChildLanes(element);

      assign(actions, {
        'lane-insert-above': {
          group: 'lane-insert-above',
          className: 'bpmn-icon-lane-insert-above',
          title: translate('Add Lane above'),
          action: {
            click: function(event, element) {
              modeling.addLane(element, 'top');
            }
          }
        }
      });

      if (childLanes.length < 2) {
        if (element.height >= 120) {
          assign(actions, {
            'lane-divide-two': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-two',
              title: translate('Divide into two Lanes'),
              action: {
                click: splitLaneHandler(2)
              }
            }
          });
        }

        if (element.height >= 180) {
          assign(actions, {
            'lane-divide-three': {
              group: 'lane-divide',
              className: 'bpmn-icon-lane-divide-three',
              title: translate('Divide into three Lanes'),
              action: {
                click: splitLaneHandler(3)
              }
            }
          });
        }
      }

      assign(actions, {
        'lane-insert-below': {
          group: 'lane-insert-below',
          className: 'bpmn-icon-lane-insert-below',
          title: translate('Add Lane below'),
          action: {
            click: function(event, element) {
              modeling.addLane(element, 'bottom');
            }
          }
        }
      });
    }

    if (is(businessObject, 'bpmn:FlowNode')) {
      if (is(businessObject, 'bpmn:EventBasedGateway')) {
        assign(actions, {
          'append.receive-task': appendAction(
            'bpmn:ReceiveTask',
            'bpmn-icon-receive-task',
            translate('Append ReceiveTask')
          ),
          'append.message-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-message',
            translate('Append MessageIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:MessageEventDefinition' }
          ),
          'append.timer-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-timer',
            translate('Append TimerIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:TimerEventDefinition' }
          ),
          'append.condition-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-condition',
            translate('Append ConditionIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
          ),
          'append.signal-intermediate-event': appendAction(
            'bpmn:IntermediateCatchEvent',
            'bpmn-icon-intermediate-event-catch-signal',
            translate('Append SignalIntermediateCatchEvent'),
            { eventDefinitionType: 'bpmn:SignalEventDefinition' }
          )
        });
      } else if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
        assign(actions, {
          'append.compensation-activity': appendAction(
            'bpmn:Task',
            'bpmn-icon-task',
            translate('Append compensation activity'),
            {
              isForCompensation: true
            }
          )
        });
      } else if (!is(businessObject, 'bpmn:EndEvent') &&
        !businessObject.isForCompensation &&
        !isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
        !isEventSubProcess(businessObject)) {
        assign(actions, {
          'append.end-event': appendAction(
            'bpmn:EndEvent',
            'bpmn-icon-end-event-none',
            translate('Append EndEvent')
          ),
          'append.gateway': appendAction(
            'bpmn:ExclusiveGateway',
            'bpmn-icon-gateway-xor',
            translate('Append Gateway')
          ),
          'append.append-task': appendAction(
            'bpmn:UserTask',
            'bpmn-icon-user-task',
            translate('Append UserTask')
          )
        });
      }
    }

    if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
      assign(actions, {
        'replace': {
          group: 'edit',
          className: 'bpmn-icon-screw-wrench',
          title: translate('Change type'),
          action: {
            click: function(event, element) {
              const position = assign(getReplaceMenuPosition(element), {
                cursor: { x: event.x, y: event.y }
              });
              popupMenu.open(element, 'bpmn-replace', position);
            }
          }
        }
      });
    }

    if (isAny(businessObject, [
      'bpmn:FlowNode',
      'bpmn:InteractionNode',
      'bpmn:DataObjectReference',
      'bpmn:DataStoreReference',
    ])) {
      assign(actions, {
        'append.text-annotation': appendGroupAction(
          'connect',
          'bpmn:TextAnnotation',
          'bpmn-icon-text-annotation'
        ),
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate(
            'Connect using ' +
            (businessObject.isForCompensation
              ? ''
              : 'Sequence/MessageFlow or ') +
            'Association'
          ),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }

    if (is(businessObject, 'bpmn:TextAnnotation')) {
      assign(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using Association'),
          action: {
            click: startConnect,
            dragstart: startConnect,
          },
        },
      });
    }

    if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
      assign(actions, {
        'connect': {
          group: 'connect',
          className: 'bpmn-icon-connection-multi',
          title: translate('Connect using DataInputAssociation'),
          action: {
            click: startConnect,
            dragstart: startConnect
          }
        }
      });
    }

    if (is(businessObject, 'bpmn:Group')) {
      assign(actions, {
        'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation')
      });
    }

    let deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });

    if (isArray(deleteAllowed)) {
      deleteAllowed = deleteAllowed[0] === element;
    }

    if (deleteAllowed) {
      assign(actions, {
        'delete': {
          group: 'edit',
          className: 'bpmn-icon-trash',
          title: translate('Remove'),
          action: {
            click: removeElement
          }
        }
      });
    }

    return actions;
  }
}

function isEventType(businessObject, type, eventDefinitionType) {
  const isType = businessObject.$instanceOf(type);
  let isDefinition = false;

  const definitions = businessObject.eventDefinitions || [];
  forEach(definitions, function(def) {
    if (def.$type === eventDefinitionType) {
      isDefinition = true;
    }
  });

  return isType && isDefinition;
} 